package utils

import (
	"fmt"
	"net/http"
	"strings"
	"time"
)

// AuthErrorHandler provides comprehensive error handling for authentication operations
type AuthErrorHandler struct {
	config     *DynamicAuthConfig
	retryCount map[string]int // sessionID -> retry count
	logger     *AuthLogger
}

// NewAuthErrorHandler creates a new authentication error handler
func NewAuthErrorHandler(config *DynamicAuthConfig, logger *AuthLogger) *AuthErrorHandler {
	return &AuthErrorHandler{
		config:     config,
		retryCount: make(map[string]int),
		logger:     logger,
	}
}

// HandleError processes authentication errors and determines appropriate recovery actions
func (aeh *AuthErrorHandler) HandleError(sessionID string, err error, context string) (*AuthErrorResponse, error) {
	if err == nil {
		return nil, nil
	}

	// Log the error with context
	aeh.logger.LogError(sessionID, err, context)

	// Check if it's an AuthError
	if authErr, ok := err.(*AuthError); ok {
		return aeh.handleAuthError(sessionID, authErr, context)
	}

	// Handle non-AuthError cases
	return aeh.handleGenericError(sessionID, err, context)
}

// handleAuthError processes specific AuthError types
func (aeh *AuthErrorHandler) handleAuthError(sessionID string, authErr *AuthError, context string) (*AuthErrorResponse, error) {
	response := &AuthErrorResponse{
		ErrorType:    authErr.Type,
		Message:      authErr.Message,
		Details:      authErr.Details,
		Context:      context,
		SessionID:    sessionID,
		Timestamp:    time.Now(),
		Recoverable:  false,
		RetryAfter:   0,
		SuggestedAction: "",
	}

	switch authErr.Type {
	case ErrTypeTokenExtraction:
		response.Recoverable = false
		response.SuggestedAction = "Check token_path configuration in OpenAPI x-mcp-auth extension"
		aeh.logger.LogWarn(sessionID, "Token extraction failed", map[string]interface{}{
			"context": context,
			"details": authErr.Details,
		})

	case ErrTypeTokenExpired:
		response.Recoverable = true
		response.RetryAfter = 0 // Immediate retry with re-authentication
		response.SuggestedAction = "Re-authenticate by calling the login endpoint"
		aeh.logger.LogInfo(sessionID, "Token expired, re-authentication required", nil)

	case ErrTypeAuthRequired:
		response.Recoverable = true
		response.RetryAfter = 0
		response.SuggestedAction = "Call the login endpoint first to obtain authentication token"
		aeh.logger.LogInfo(sessionID, "Authentication required", map[string]interface{}{
			"context": context,
		})

	case ErrTypeConfigInvalid:
		response.Recoverable = false
		response.SuggestedAction = "Fix the authentication configuration in OpenAPI specification"
		aeh.logger.LogError(sessionID, fmt.Errorf("invalid auth config: %s", authErr.Details), context)

	case ErrTypeTokenInvalid:
		response.Recoverable = aeh.shouldRetryLogin(sessionID)
		if response.Recoverable {
			response.RetryAfter = time.Second * 5
			response.SuggestedAction = "Re-authenticate with valid credentials"
		} else {
			response.SuggestedAction = "Check authentication credentials and token format"
		}
		aeh.logger.LogWarn(sessionID, "Invalid token format", map[string]interface{}{
			"context": context,
			"details": authErr.Details,
		})

	default:
		response.SuggestedAction = "Check authentication configuration and credentials"
		aeh.logger.LogError(sessionID, fmt.Errorf("unknown auth error type: %s", authErr.Type), context)
	}

	return response, nil
}

// handleGenericError processes non-AuthError cases
func (aeh *AuthErrorHandler) handleGenericError(sessionID string, err error, context string) (*AuthErrorResponse, error) {
	response := &AuthErrorResponse{
		ErrorType:       "generic_error",
		Message:         "Authentication operation failed",
		Details:         err.Error(),
		Context:         context,
		SessionID:       sessionID,
		Timestamp:       time.Now(),
		Recoverable:     false,
		RetryAfter:      0,
		SuggestedAction: "Check system logs for detailed error information",
	}

	// Check for network-related errors
	if isNetworkError(err) {
		response.ErrorType = "network_error"
		response.Recoverable = true
		response.RetryAfter = time.Second * 10
		response.SuggestedAction = "Check network connectivity and API endpoint availability"
		aeh.logger.LogWarn(sessionID, "Network error during authentication", map[string]interface{}{
			"error": err.Error(),
			"context": context,
		})
	} else {
		aeh.logger.LogError(sessionID, err, context)
	}

	return response, nil
}

// shouldRetryLogin determines if login should be retried based on retry count
func (aeh *AuthErrorHandler) shouldRetryLogin(sessionID string) bool {
	maxRetries := 3
	currentRetries := aeh.retryCount[sessionID]
	
	if currentRetries >= maxRetries {
		return false
	}
	
	aeh.retryCount[sessionID] = currentRetries + 1
	return true
}

// ResetRetryCount resets the retry count for a session (call after successful authentication)
func (aeh *AuthErrorHandler) ResetRetryCount(sessionID string) {
	delete(aeh.retryCount, sessionID)
}

// CleanupSession removes session-specific error tracking data
func (aeh *AuthErrorHandler) CleanupSession(sessionID string) {
	delete(aeh.retryCount, sessionID)
	aeh.logger.LogDebug(sessionID, "Cleaned up error handler session data", nil)
}

// AuthErrorResponse represents a structured error response with recovery information
type AuthErrorResponse struct {
	ErrorType       string        `json:"error_type"`
	Message         string        `json:"message"`
	Details         string        `json:"details,omitempty"`
	Context         string        `json:"context"`
	SessionID       string        `json:"session_id"`
	Timestamp       time.Time     `json:"timestamp"`
	Recoverable     bool          `json:"recoverable"`
	RetryAfter      time.Duration `json:"retry_after"`
	SuggestedAction string        `json:"suggested_action"`
}

// ToHTTPStatus converts AuthError type to appropriate HTTP status code
func (aer *AuthErrorResponse) ToHTTPStatus() int {
	switch aer.ErrorType {
	case ErrTypeAuthRequired:
		return http.StatusUnauthorized
	case ErrTypeTokenExpired:
		return http.StatusUnauthorized
	case ErrTypeTokenInvalid:
		return http.StatusUnauthorized
	case ErrTypeConfigInvalid:
		return http.StatusBadRequest
	case ErrTypeTokenExtraction:
		return http.StatusInternalServerError
	case "network_error":
		return http.StatusBadGateway
	default:
		return http.StatusInternalServerError
	}
}

// IsRecoverable returns true if the error can potentially be recovered from
func (aer *AuthErrorResponse) IsRecoverable() bool {
	return aer.Recoverable
}

// GetRetryDelay returns the recommended delay before retrying
func (aer *AuthErrorResponse) GetRetryDelay() time.Duration {
	return aer.RetryAfter
}

// isNetworkError checks if an error is network-related
func isNetworkError(err error) bool {
	if err == nil {
		return false
	}
	
	errStr := strings.ToLower(err.Error())
	networkKeywords := []string{
		"connection refused",
		"connection timeout",
		"network unreachable",
		"no such host",
		"timeout",
		"connection reset",
		"eof",
	}
	
	for _, keyword := range networkKeywords {
		if strings.Contains(errStr, keyword) {
			return true
		}
	}
	
	return false
}

// WrapError wraps a generic error as an AuthError with additional context
func WrapError(errType, message string, originalErr error) *AuthError {
	details := ""
	if originalErr != nil {
		details = originalErr.Error()
	}
	return NewAuthError(errType, message, details)
}

// WrapTokenExtractionError creates a specific error for token extraction failures
func WrapTokenExtractionError(tokenPath string, originalErr error) *AuthError {
	message := fmt.Sprintf("Failed to extract token from path: %s", tokenPath)
	return WrapError(ErrTypeTokenExtraction, message, originalErr)
}

// WrapConfigValidationError creates a specific error for configuration validation failures
func WrapConfigValidationError(configField string, originalErr error) *AuthError {
	message := fmt.Sprintf("Invalid configuration for field: %s", configField)
	return WrapError(ErrTypeConfigInvalid, message, originalErr)
}

// WrapTokenValidationError creates a specific error for token validation failures
func WrapTokenValidationError(tokenType string, originalErr error) *AuthError {
	message := fmt.Sprintf("Invalid token format for type: %s", tokenType)
	return WrapError(ErrTypeTokenInvalid, message, originalErr)
}

// CreateAuthRequiredError creates an error indicating authentication is required
func CreateAuthRequiredError(requiredAuthTypes []string) *AuthError {
	message := "Authentication required"
	details := ""
	if len(requiredAuthTypes) > 0 {
		details = fmt.Sprintf("Required auth types: %s", strings.Join(requiredAuthTypes, ", "))
	}
	return NewAuthError(ErrTypeAuthRequired, message, details)
}

// CreateTokenExpiredError creates an error indicating the token has expired
func CreateTokenExpiredError(authType string, expiredAt time.Time) *AuthError {
	message := fmt.Sprintf("Authentication token expired for type: %s", authType)
	details := fmt.Sprintf("Token expired at: %s", expiredAt.Format(time.RFC3339))
	return NewAuthError(ErrTypeTokenExpired, message, details)
}